
#include "../iface/LibSSH2Interface.h"
#include "../aprtools/APRFile.h"
#include "../aprtools/APRAutoLock.h"
#include "SCPReceiveChannel.h"

/** Initialisation for the secure copy channel, from remote to local.
 * 
 * @param remotefile The place on the remote file system to read from.
 * 
 * @param remotefilelength The length of the remote file to read in
 * bytes.
 * 
 * @param remoteaccess The octal access permissions for the remote
 * file.
 * 
 * @return The newly allocated SSH channel. */
LIBSSH2_CHANNEL *SCPReceiveChannel::initialiseSCPReceive(const char *remotefile, int64_t *remotefilelength, int *remoteaccess)
{
   if (!remotefile) { errprint("remote file name is NULL"); return NULL; }
   if (tclient->isUp())
   {
      struct stat remote_file_stat;
      APRAutoLock autolock(tclient->getMutex());
      LIBSSH2_CHANNEL *tchannel = libssh2_scp_recv(const_cast<LIBSSH2_SESSION *>(tclient->getSSHSession()), remotefile, &remote_file_stat);
      if (tchannel == NULL)
      {
         elCheckLibSSH2Error(tclient);
         return NULL;
      }
      if (remotefilelength)
         *remotefilelength = remote_file_stat.st_size;
      if (remoteaccess)
         *remoteaccess = remote_file_stat.st_mode;
      return tchannel;
   } else
   {
      errprint("SCPReceiveChannel (secure copy) can not be established - SSH client not connected");
      tclient = NULL;
      return NULL;
   }
}

/** Constructor for the secure copy channel.
 * 
 * @param itclient The SSH client object to use to create the secure
 * copy channel. */
SCPReceiveChannel::SCPReceiveChannel(SSHClient &itclient) : SSHChannel(itclient)
{
}

/** Destructor for the secure copy channel. */
SCPReceiveChannel::~SCPReceiveChannel()
{
   // This should not run until the future has finished - corruption
   // could result!
}

/** Copy the remote file to a local file with the same permissions.
 * 
 * @param tchannel SSH channel to use.
 * 
 * @param remotefile Name and path for the remote file.
 * 
 * @param remotefilelength Length of the remote file.
 * 
 * @param remoteaccess Access modifier on the remote file.
 * 
 * @param localfile Name and path for the local file.
 * 
 * @return Zero on success, non-zero on failure. */
int SCPReceiveChannel::getFileFromRemoteFile(LIBSSH2_CHANNEL *tchannel, const char *remotefile, int64_t remotefilelength, int remoteaccess,
   const char *localfile)
{
   if (!remotefile) { errprint("remote file name is NULL"); return -1; }
   if (!localfile) { errprint("local file name is NULL"); return -1; }
   if (tchannel == NULL)
   {
      errprint("could not open SCP channel");
      return -1;
   }
   apr_status_t status;
   apr_file_t *newFP;
   status = APRFile::openWrapper(&newFP, localfile, APR_FOPEN_WRITE|APR_FOPEN_CREATE|APR_FOPEN_TRUNCATE|APR_FOPEN_BINARY|APR_FOPEN_BUFFERED,
      (remoteaccess & 0x7) | ((remoteaccess & 0x38) << 1) | ((remoteaccess & 0x1A0) << 2), imp);
   if (status != APR_SUCCESS) { CheckAPRError(status); return -1; }
   apr_off_t remaining = remotefilelength;
   char *filebuffer = (char *)malloc(sizeof(char)*SCP_FILEBUFFER);
   while (remaining > 0)
   {
      apr_size_t realwrite = (remaining > SCP_FILEBUFFER) ? SCP_FILEBUFFER : remaining;
      ssize_t sofarread = 0;
      int timeout = 1;
      while (sofarread < realwrite)
      {
         ssize_t sread = 0;
         {
            APRAutoLock autolock(tclient->getMutex());
            sread = libssh2_channel_read_ex(tchannel, 0, &(filebuffer[sofarread]), SCP_FILEBUFFER - sofarread);
         }
         if ((sread < 0) && (sread != LIBSSH2_ERROR_EAGAIN))
         {
            {
               APRAutoLock autolock(tclient->getMutex());
               elCheckLibSSH2Error(tclient);
            }
            errprint("error reading from secure copy stream");
            status = apr_file_close(newFP);
            CheckAPRError(status);
            free(filebuffer);
            return -1;
         } else if (sread == LIBSSH2_ERROR_EAGAIN)
         {
            timeout <<= 1;
            timeout = (timeout > SSH_TIMEOUT_EAGAIN) ? SSH_TIMEOUT_EAGAIN : timeout;
            apr_sleep(timeout);
         } else if (sread >= 0)
            sofarread += sread;
      }
      apr_size_t fullwrite = realwrite;
      status = apr_file_write_full(newFP, filebuffer, realwrite, &fullwrite);
      CheckAPRError(status);
      if (fullwrite != realwrite)
      {
         errprint("error writing file '%s' - expected to write %" APR_SIZE_T_FMT ", actually wrote %" APR_SIZE_T_FMT " bytes.", localfile, realwrite, fullwrite);
         status = apr_file_close(newFP);
         CheckAPRError(status);
         free(filebuffer);
         return -1;
      }
      remaining -= fullwrite;
   }
   free(filebuffer);
   status = apr_file_close(newFP);
   CheckAPRError(status);
   return 0;
}

/** Get the remote file as a local buffer.
 * 
 * @param tchannel SSH channel to use.
 * 
 * @param remotefile File in the remote filesystem to read from.
 * 
 * @param remotefilelength Length of the remote file.
 * 
 * @param remoteaccess Access modifier on the remote file.
 * 
 * @param localbufferlen Length of the local buffer of data that was
 * written.
 * 
 * @return Zero on success, non-zero on failure. */
char *SCPReceiveChannel::getBufferFromRemoteFile(LIBSSH2_CHANNEL *tchannel, const char *remotefile, int64_t remotefilelength, int remoteaccess, int64_t *localbufferlen)
{
   if (!remotefile) { errprint("remote file name is NULL"); return NULL; }
   if (tchannel == NULL)
   {
      errprint("could not open SCP channel");
      return NULL;
   }
   char *recvbuffer = (char *)malloc(sizeof(char)*remotefilelength);
   apr_off_t sofarread = 0;
   int timeout = 1;
   while (sofarread < remotefilelength)
   {
      apr_size_t realread = ((remotefilelength - sofarread) > SCP_FILEBUFFER) ? SCP_FILEBUFFER : (remotefilelength - sofarread);
      ssize_t reading = 0;
      {
         APRAutoLock autolock(tclient->getMutex());
         reading = libssh2_channel_read_ex(tchannel, 0, &(recvbuffer[sofarread]), realread);
      }
      if ((reading < 0) && (reading != LIBSSH2_ERROR_EAGAIN))
      {
         {
            APRAutoLock autolock(tclient->getMutex());
            elCheckLibSSH2Error(tclient);
         }
         errprint("error writing to secure copy stream");
         free(recvbuffer);
         return NULL;
      } else if (reading == LIBSSH2_ERROR_EAGAIN)
      {
         timeout <<= 1;
         timeout = (timeout > SSH_TIMEOUT_EAGAIN) ? SSH_TIMEOUT_EAGAIN : timeout;
         apr_sleep(timeout);
      } else if (reading >= 0)
         sofarread += reading;
   }
   *localbufferlen = sofarread;
   return recvbuffer;
}

/** Copy the remote file to a local file with the given permissions,
 * with the result as a future.
 * 
 * @param remotefile Name and path for the remote file.
 * 
 * @param localfile Name and path for the local file.
 * 
 * @param remoteaccess Access permissions for the remote file.
 * 
 * @return The future which has an integer which is zero on success
 * and non-zero on failure. */
APRFuture<int> *SCPReceiveChannel::getFileFromRemoteFileFuture(const char *remotefile, const char *localfile, int remoteaccess)
{
   int64_t remotefilelength;
   int tremoteaccess;
   LIBSSH2_CHANNEL *tchannel = initialiseSCPReceive(remotefile, &remotefilelength, &tremoteaccess);
   if (tchannel == NULL)
   {
      errprint("channel failed - SCP receive has no future");
      return NULL;
   }
   APRFuture<int> *remote_copy_result_future = new APRFuture<int>(tclient->getMemoryPool());
   remote_copy_result_future->addArgument((void *)this);
   remote_copy_result_future->addArgument(tchannel);
   remote_copy_result_future->addArgument(remotefile);
   remote_copy_result_future->addArgument((apr_off_t)remotefilelength);
   // accept the local concept of access rights, after all.
   remote_copy_result_future->addArgument(localfile);
   remote_copy_result_future->addArgument(remoteaccess);
   remote_copy_result_future->addFunctor(getGetFileFromRemoteFileThread());
   remote_copy_result_future->addDestructor(stopChannelFuture);
   remote_copy_result_future->start();
   return remote_copy_result_future;
}

/** Copy the remote file to a local buffer, yielding a result as a
 * future.
 * 
 * @param remotefile File in the remote filesystem to write to.
 * 
 * @param localbufferlen Length of the local buffer of data to read
 * into the local buffer.
 * 
 * @param remoteaccess Access permissions for the remote file.
 * 
 * @return A future containing a integer when complete. This is then
 * zero on success, non-zero on failure. */
APRFuture<char *> *SCPReceiveChannel::getBufferFromRemoteFileFuture(const char *remotefile, int64_t *localbufferlen, int remoteaccess)
{
   int64_t remotefilelength;
   int tremoteaccess;
   LIBSSH2_CHANNEL *tchannel = initialiseSCPReceive(remotefile, &remotefilelength, &tremoteaccess);
   if (tchannel == NULL)
   {
      errprint("channel failed - SCP receive has no future");
      return NULL;
   }
   APRFuture<char *> *remote_copy_result_future = new APRFuture<char *>(tclient->getMemoryPool());
   remote_copy_result_future->addArgument((void *)this);
   remote_copy_result_future->addArgument(tchannel);
   remote_copy_result_future->addArgument(remotefile);
   remote_copy_result_future->addArgument((apr_off_t)remotefilelength);
   remote_copy_result_future->addArgument((void *)localbufferlen);
   remote_copy_result_future->addArgument(remoteaccess);
   remote_copy_result_future->addFunctor(getGetBufferFromRemoteFileThread());
   remote_copy_result_future->addDestructor(stopChannelFuture);
   remote_copy_result_future->start();
   return remote_copy_result_future;
}

/** A thread wrapper around the copy remote file to local file
 * function.
 * 
 * @param tthread Thread identifier from APR.
 * 
 * @param args Encoded arguments.
 * 
 * @return A pointer containing the return type in such a way that it
 * can be deleted by a future later. */
void *SCPReceiveChannel::getFileFromRemoteFileThread(apr_thread_t *tthread, void *args)
{
   SCPReceiveChannel *kthis = (SCPReceiveChannel *)((void **)args)[0];
   LIBSSH2_CHANNEL *tchannel = (LIBSSH2_CHANNEL *)((void **)args)[1];
   const char *remotefile = (const char *)(((void **)args)[2]);
   apr_off_t remotefilelength = *((apr_off_t *)(((void **)args)[3]));
   const char *localfile = (const char *)(((void **)args)[4]);
   int remoteaccess = *((int *)(((void **)args)[5]));
   int *nret = new (int)();
   *nret = kthis->getFileFromRemoteFile(tchannel, remotefile, remotefilelength, remoteaccess, localfile);
   return nret;
}

/** A thread wrapper around the copy remote file to local buffer
 * function.
 * 
 * @param tthread Thread identifier from APR.
 * 
 * @param args Encoded arguments.
 * 
 * @return A pointer containing the return type in such a way that it
 * can be deleted by a future later. */
void *SCPReceiveChannel::getBufferFromRemoteFileThread(apr_thread_t *tthread, void *args)
{
   SCPReceiveChannel *kthis = (SCPReceiveChannel *)((void **)args)[0];
   LIBSSH2_CHANNEL *tchannel = (LIBSSH2_CHANNEL *)((void **)args)[1];
   const char *remotefile = (const char *)(((void **)args)[2]);
   int64_t remotefilelength = (int64_t)(*((apr_off_t *)(((void **)args)[3])));
   int64_t *localbufferlength = (int64_t *)(((void **)args)[4]);
   int remoteaccess = *((int *)(((void **)args)[5]));
   char **nret = new (char *)();
   *nret = kthis->getBufferFromRemoteFile(tchannel, remotefile, remotefilelength, remoteaccess, localbufferlength);
   return nret;
}
